import sys, os, json, subprocess
from PySide6.QtWidgets import QApplication, QWidget, QSystemTrayIcon, QMenu, QMessageBox, QWidgetAction, QHBoxLayout, QPushButton, QLabel, QStyle
from PySide6.QtGui import QIcon, QAction
from PySide6.QtCore import QFile, QTextStream
import psutil

# 全局变量记录应用程序是否已启动
APP_RUNNING = False
# 常量定义：用于标识菜单项类型、标签、图标、命令和子菜单项
TYPE_KEY = 'type'
LABEL_KEY = 'label'
ICON_KEY = 'icon'
COMMAND_KEY = 'command'
CHILDREN_KEY = 'children'

class SingleInstance:
    def __init__(self, name):
        self.name = name

    def is_already_running(self):
        # 获取当前进程ID
        current_pid = os.getpid()
        print(current_pid)
        # 搜索与当前进程相同名称的所有进程
        for proc in psutil.process_iter(['pid', 'name']):
            # 如果进程名称匹配且PID不等于当前进程ID，则认为有其他实例正在运行
            if proc.info['name'] == self.name and proc.pid != current_pid:
                return True
        return False
    
class MyWindow(QWidget):
    """
    主窗口类，实现系统托盘图标及其相关功能。
    """
    def __init__(self):
        """
        初始化主窗口，创建系统托盘图标。
        """
        super().__init__()
        
        self.initUI()

    def initUI(self):
        """
        初始化用户界面，包括设置系统托盘图标和关联其上下文菜单。
        """
        # 创建系统托盘图标，设置图标和提示信息
        self.tray_icon = QSystemTrayIcon(QIcon(os.path.join(os.path.dirname(__file__), '公众号.png')), self)
        self.tray_icon.setToolTip(u'灵屿工具')

        # 创建并初始化系统托盘菜单
        self.tray_menu = TrayMenu(self)
        self.tray_icon.setContextMenu(self.tray_menu)

        # 设置系统托盘图标激活时的回调函数
        self.tray_icon.activated.connect(self.overlay_activated)

    def main(self):
        """
        显示系统托盘图标并向用户显示启动成功消息。
        """
        self.tray_icon.show()
        # 使用Singleton模式检查是否已有实例运行
        single_instance_checker = SingleInstance("python.exe")
        if single_instance_checker.is_already_running():
            # 已经有实例运行，可以在这里发送警告、显示消息或退出
            self.tray_icon.showMessage(u"灵屿工具","程序已经有一个实例在运行，请勿重复打开！", icon=QSystemTrayIcon.MessageIcon.Information)
        else:
            self.tray_icon.showMessage(u"灵屿工具", u'启动成功', icon=QSystemTrayIcon.MessageIcon.Information)
       
    def overlay_activated(self, reason):
        """
        根据系统托盘图标被激活的原因执行相应操作。

        :param reason: QSystemTrayIcon.ActivationReason 类型，表示激活原因。
        """
        if reason == QSystemTrayIcon.ActivationReason.Trigger:
            pass  # 在此处可添加单击托盘图标时要执行的操作
        elif reason == QSystemTrayIcon.ActivationReason.Context:
            self.tray_menu.clear()  # 清除原有菜单
            self.tray_menu.build_menu()  # 重新构建菜单
        elif reason == QSystemTrayIcon.ActivationReason.DoubleClick:
            pass  # 在此处可添加双击托盘图标时要执行的操作
        elif reason == QSystemTrayIcon.ActivationReason.MiddleClick:
            pass  # 在此处可添加中键点击托盘图标时要执行的操作

class TrayMenu(QMenu):
    """
    系统托盘菜单类，负责菜单的动态构建和关联操作。
    """
    def __init__(self, parent_obj):
        """
        初始化菜单类，加载菜单配置文件。

        :param parent_obj: 父窗口对象。
        """
        super().__init__()
        self.parent_obj = parent_obj
        self.TD_path = os.path.dirname(__file__).replace('\\', '/').rsplit('/', 2)[0]
        self.json_path = os.path.join(os.path.dirname(__file__), 'SysTray.json')
        self.build_menu()

    def recursive(self, item_list, parent):
        """
        递归地从菜单项列表构建菜单结构。

        :param item_list: 包含菜单项配置的列表。
        :param parent: 父菜单对象。
        """
        for i in item_list:
            if i[TYPE_KEY] == 'menu':  # 如果是菜单类型
                menu = QMenu(parent)
                menu.setTitle(i[LABEL_KEY])  # 设置菜单标题
                if ICON_KEY in i:  # 如果存在图标配置
                    menu.setIcon(QIcon(i[ICON_KEY]))  # 设置菜单图标
                parent.addMenu(menu)  # 将新菜单添加至父菜单
                self.recursive(i[CHILDREN_KEY], menu)  # 递归处理子菜单项
            elif i[TYPE_KEY] == 'action':  # 如果是动作类型
                action = QAction(i[LABEL_KEY], self)
                self.build_action(action, i)  # 构建并关联动作
                parent.addAction(action)  # 将动作添加至父菜单

    def build_action(self, action, i):
        """
        为给定的QAction实例绑定相应的操作。

        :param action: QAction 对象。
        :param i: 表示动作项的字典对象。
        """
        command = i[COMMAND_KEY]
        RootDirectory = os.path.dirname(os.path.dirname(__file__))
        FullPath = os.path.join(RootDirectory, command).replace('\\', '/')
        action.triggered.connect(lambda *args: self.launch_program(RootDirectory, FullPath))  # 当动作触发时，执行启动程序的操作

    def build_menu(self):
        """
        根据 JSON 配置文件构建菜单结构，并加载样式表。
        """
        with open(self.json_path, "r", encoding='utf8') as f:
            try:
                menu_list = json.load(f)
            except (FileNotFoundError, json.JSONDecodeError) as e:
                print(f"Error loading menu configuration: {e}")
                return

        self.recursive(menu_list, self)  # 递归构建菜单
        self.styles_css_path = '{}/styles.css'.format(os.path.dirname(__file__))
        exit_action = QAction('退出(&Q)', self)
        exit_action.triggered.connect(QApplication.instance().quit)  # 添加“退出”菜单项，当触发时关闭应用程序
        self.addAction(exit_action)
        self.loadStylesheet(self.styles_css_path, self)  # 加载并应用样式表

    def launch_program(self, RootDirectory, command):
        """
        启动指定的外部程序。

        :param RootDirectory: 程序根目录路径。
        :param command: 启动命令。
        """
        try:
            subprocess.Popen([os.path.join(os.path.dirname(RootDirectory), '.venv/Scripts/python.exe'), command],
                             creationflags=subprocess.CREATE_NO_WINDOW, stdout=subprocess.DEVNULL)
        except FileNotFoundError as e:
            print(f"Error launching program: {e}")

    def loadStylesheet(self, file_path, widget):
        """
        加载指定路径的CSS样式表文件，并将其应用到指定的QWidget上。

        :param file_path: 样式表文件路径。
        :param widget: 要应用样式的QWidget对象。
        """
        file = QFile(file_path)
        if not file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text):
            print(f"Failed to open stylesheet at path: {file_path}")
            return

        stream = QTextStream(file)
        style_sheet = stream.readAll()

        if not style_sheet:
            print(f"Stylesheet is empty or could not be read from {file_path}")
        else:
            widget.setStyleSheet(style_sheet)

        file.close()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.main()
    sys.exit(app.exec())